home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2007 December / PCWKCD1207B.iso / Blogowanie poza sfera / Flock 0.9.1.3 stable / flock-0.9.1.3.en-US.win32.exe / flock / components / flockWebDetective.js < prev    next >
Text File  |  2007-10-12  |  64KB  |  1,902 lines

  1. // vim: ts=2 sw=2 expandtab cindent
  2. //
  3. // BEGIN FLOCK GPL
  4. //
  5. // Copyright Flock Inc. 2005-2007
  6. // http://flock.com
  7. //
  8. // This file may be used under the terms of of the
  9. // GNU General Public License Version 2 or later (the "GPL"),
  10. // http://www.gnu.org/licenses/gpl.html
  11. //
  12. // Software distributed under the License is distributed on an "AS IS" basis,
  13. // WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  14. // for the specific language governing rights and limitations under the
  15. // License.
  16. //
  17. // END FLOCK GPL
  18. //
  19.  
  20. const Cc = Components.classes;
  21. const Ci = Components.interfaces;
  22. const Cr = Components.results;
  23.  
  24.  
  25. const ENABLE_DEBUG = false; // switch to turn off slow debug code for production
  26. function DEBUG(x) { if (ENABLE_DEBUG) debug("flockWebDetective: "+x+"\n"); }
  27.  
  28. const CLASS_ID = Components.ID("{61F83B70-6B52-11DB-BD13-0800200C9A66}");
  29. const CLASS_NAME = "Flock Web Detective";
  30. const CONTRACT_ID = "@flock.com/web-detective;1";
  31. const INTERFACES = [
  32.   Components.interfaces.nsISupports,
  33.   Components.interfaces.nsIClassInfo,
  34.   Components.interfaces.nsIObserver,
  35.   Components.interfaces.nsITimerCallback,
  36.   Components.interfaces.flockIWebDetective
  37. ];
  38.  
  39. // from nspr's prio.h
  40. const PR_RDONLY      = 0x01;
  41. const PR_WRONLY      = 0x02;
  42. const PR_RDWR        = 0x04;
  43. const PR_CREATE_FILE = 0x08;
  44. const PR_APPEND      = 0x10;
  45. const PR_TRUNCATE    = 0x20;
  46. const PR_SYNC        = 0x40;
  47. const PR_EXCL        = 0x80;
  48.  
  49. const DEFAULT_UPDATE_SERVER = "https://extensions.flock.com/webdetective/dove/";
  50. const DEFAULT_UPDATE_INTERVAL = 1; // This is in days
  51.  
  52.  
  53. // ===================================================
  54. // ========== BEGIN flockWebDetective class ==========
  55. // ===================================================
  56.  
  57. function flockWebDetective()
  58. {
  59.   // Associative array where keys are service names and values are XML Docs
  60.   this.mRules = [];
  61.  
  62.   // Associative array of version strings by service
  63.   this.mVersions = [];
  64.  
  65.   // Associative array of strings by service
  66.   this.mStrings = [];
  67.  
  68.   // Associative array of session cookies by service
  69.   this.mSessionCookies = [];
  70.  
  71.   // Associative array of detect files that we've loaded for a given service
  72.   this.mDetectFiles = [];
  73.  
  74.   this.cookieMgr = Components.classes["@mozilla.org/cookiemanager;1"]
  75.                              .getService(Components.interfaces.nsICookieManager);
  76.   this.mEnabled = true;
  77.  
  78.   this.startUpdateService();
  79. }
  80.  
  81. // BEGIN nsISupports interface
  82. flockWebDetective.prototype.QueryInterface =
  83. function flockWebDetective_QueryInterface(aIID)
  84. {
  85.   var interfaces = INTERFACES;
  86.   for (var i in interfaces) {
  87.     if (aIID.equals(interfaces[i])) {
  88.       return this;
  89.     }
  90.   }
  91.   throw Components.results.NS_ERROR_NO_INTERFACE;
  92. }
  93. // END nsISupports interface
  94.  
  95.  
  96. // BEGIN nsIClassInfo interface
  97. flockWebDetective.prototype.contractID = CONTRACT_ID;
  98. flockWebDetective.prototype.classID = CLASS_ID;
  99. flockWebDetective.prototype.classDescription = CLASS_NAME;
  100. flockWebDetective.prototype.implementationLanguage = Components.interfaces.nsIProgrammingLanguage.JAVASCRIPT;
  101. flockWebDetective.prototype.flags = Components.interfaces.nsIClassInfo.SINGLETON;
  102.  
  103. flockWebDetective.prototype.getInterfaces =
  104. function flockWebDetective_getInterfaces(aCount)
  105. {
  106.   var interfaces = INTERFACES;
  107.   aCount.value = interfaces.length;
  108.   return interfaces;
  109. }
  110.  
  111. flockWebDetective.prototype.getHelperForLanguage =
  112. function flockWebDetective_getHelperForLanguage(aLanguage)
  113. {
  114.   return null;
  115. }
  116. // END nsIClassInfo interface
  117.  
  118.  
  119. // BEGIN nsIObserver interface
  120. flockWebDetective.prototype.observe =
  121. function flockWebDetective_observe(aSubject, aTopic, aData)
  122. {
  123. }
  124. // END nsIObserver interface
  125.  
  126.  
  127. // BEGIN nsITimerCallback interface
  128. flockWebDetective.prototype.notify =
  129. function flockWebDetective_notify(timer)
  130. {
  131.   try {
  132.     var prefs = Cc['@mozilla.org/preferences-service;1']
  133.       .getService(Ci.nsIPrefBranch)
  134.     var doUpdate = prefs.getBoolPref('flock.service.webdetective.update');
  135.     if (!doUpdate)
  136.       return;
  137.   }
  138.   catch (e) { }
  139.  
  140.   this.checkForUpdates();
  141. }
  142. // END nsITimerCallback interface
  143.  
  144.  
  145. // BEGIN flockIWebDetective interface
  146. flockWebDetective.prototype.detect =
  147. function flockWebDetective_detect(aServiceName, aType, aDocument, aResults)
  148. {
  149.   DEBUG("{flockIWebDetective}.detect('"+aServiceName+"', '"+aType+"')");
  150.   return this.innerDetect(aServiceName, aType, aDocument, null, aResults);
  151. }
  152.  
  153. flockWebDetective.prototype.detectForm =
  154. function flockWebDetective_detectForm(aServiceName, aType, aForm, aResults)
  155. {
  156.   DEBUG("{flockIWebDetective}.detectForm('"+aServiceName+"', '"+aType+"')");
  157.   aForm.QueryInterface(Components.interfaces.nsIDOMHTMLFormElement);
  158.   return this.innerDetect(aServiceName, aType, aForm.ownerDocument, aForm, aResults);
  159. }
  160.  
  161. flockWebDetective.prototype.detectCookies =
  162. function flockWebDetective_detectCookies(aServiceName, aType, aResults)
  163. {
  164.   DEBUG("{flockIWebDetective}.detectCookies('"+aServiceName+"', '"+aType+"')");
  165.   return this.innerDetect(aServiceName, aType, null, null, aResults);
  166. }
  167.  
  168. flockWebDetective.prototype.detectNoDOM =
  169. function flockWebDetective_detectNoDOM(aServiceName, aType, aURL, aDocumentText, aResults)
  170. {
  171.   DEBUG("{flockIWebDetective}.detectNoDOM('"+aServiceName+"', '"+aType+"')");
  172.   //DEBUG(aDocumentText);
  173.   var doc = {
  174.     noDOM: true,
  175.     documentElement: {
  176.       innerHTML: aDocumentText
  177.     },
  178.     URL: aURL
  179.   };
  180.   return this.innerDetect(aServiceName, aType, doc, null, aResults);
  181. }
  182.  
  183. flockWebDetective.prototype.getSessionCookies =
  184. function flockWebDetective_getSessionCookies(aServiceName)
  185. {
  186.   if (!this.mSessionCookies[aServiceName]) {
  187.     this.loadSessionCookies(aServiceName);
  188.   }
  189.   if (!this.mSessionCookies[aServiceName]) return null;
  190.   var wd = this;
  191.   return {
  192.     arr: wd.mSessionCookies[aServiceName],
  193.     idx: 0,
  194.     QueryInterface: function (aIID) {
  195.       if ( !aIID.equals(Components.interfaces.nsISupports) &&
  196.            !aIID.equals(Components.interfaces.nsISimpleEnumerator) )
  197.       {
  198.         throw Components.results.NS_ERROR_NO_INTERFACE;
  199.       }
  200.       return this;
  201.     },
  202.     hasMoreElements: function () {
  203.       return (this.idx < this.arr.length);
  204.     },
  205.     getNext: function () {
  206.       var c = this.arr[this.idx++];
  207.       var cookie = {
  208.         QueryInterface: function (aIID) {
  209.           if ( !aIID.equals(Components.interfaces.nsISupports) &&
  210.                !aIID.equals(Components.interfaces.nsICookie) )
  211.           {
  212.             throw Components.results.NS_ERROR_NO_INTERFACE;
  213.           }
  214.           return this;
  215.         },
  216.         host: c.host,
  217.         name: c.name,
  218.         path: c.path
  219.       };
  220.       return cookie.QueryInterface(Components.interfaces.nsICookie);
  221.     }
  222.   };
  223. }
  224.  
  225. flockWebDetective.prototype.getString =
  226. function flockWebDetective_getString(aServiceName, aURLName, aDefault)
  227. {
  228.   DEBUG("{flockIWebDetective}.getString('"+aServiceName+"', '"+aURLName+"')");
  229.   if (!this.mEnabled) return aDefault;
  230.   if (!this.mRules[aServiceName]) {
  231.     throw "No rules loaded for service: "+aServiceName;
  232.   }
  233.   if (!this.mStrings[aServiceName]) {
  234.     this.loadStrings(aServiceName);
  235.   }
  236.   if (this.mStrings[aServiceName][aURLName]) {
  237.     return this.mStrings[aServiceName][aURLName];
  238.   }
  239.   return aDefault;
  240. }
  241.  
  242. flockWebDetective.prototype.loadDetectFile =
  243. function flockWebDetective_loadDetectFile(aDetectFile)
  244. {
  245.   DEBUG("{flockIWebDetective}.loadDetectFile()");
  246.  
  247.   var prefs = Components.classes["@mozilla.org/preferences-service;1"]
  248.                 .getService(Components.interfaces.nsIPrefService)
  249.                 .getBranch(null);
  250.   if (prefs.getPrefType("flock.service.webdetective.enabled")) {
  251.     this.mEnabled = prefs.getBoolPref("flock.service.webdetective.enabled");
  252.   }
  253.   if (!this.mEnabled) return;
  254.  
  255.   aDetectFile.QueryInterface(Components.interfaces.nsILocalFile);
  256.  
  257.   if ((!aDetectFile.exists()) || (!aDetectFile.isReadable())) {
  258.     throw Components.results.NS_ERROR_UNEXPECTED;
  259.   }
  260.  
  261.   // Load and parse the XML
  262.   var fis = Components.classes["@mozilla.org/network/file-input-stream;1"]
  263.                       .createInstance(Components.interfaces.nsIFileInputStream);
  264.   fis.init(aDetectFile, PR_RDONLY, 0, 0);
  265.   DEBUG("available bytes: "+fis.available());
  266.   var domParser = Components.classes["@mozilla.org/xmlextras/domparser;1"]
  267.                             .createInstance(Components.interfaces.nsIDOMParser);
  268.   var xmlDoc = domParser.parseFromStream(fis, "UTF-8", fis.available(), "text/xml");
  269.  
  270.   var serviceEl = xmlDoc.documentElement;
  271.   if (serviceEl.tagName != "service") {
  272.     DEBUG("PARSE ERROR: no 'service' element found");
  273.     throw Components.results.NS_ERROR_UNEXPECTED;
  274.   }
  275.   var serviceName = serviceEl.getAttribute("name");
  276.   if (!serviceName || (serviceName == "")) {
  277.     DEBUG("PARSE ERROR: 'service' element has no name");
  278.     throw Components.results.NS_ERROR_UNEXPECTED;
  279.   }
  280.   this.mRules[serviceName] = xmlDoc;
  281.   this.mDetectFiles[serviceName] = aDetectFile;
  282.   if (serviceEl.hasAttribute("version")) {
  283.     this.mVersions[serviceName] = serviceEl.getAttribute("version");
  284.   }
  285.   DEBUG("loaded detection rules for service: "+serviceName);
  286.   // Force the strings and cookies to be reloaded, since they may have changed
  287.   this.mStrings[serviceName] = null;
  288.   this.mSessionCookies[serviceName] = null;
  289. }
  290.  
  291. flockWebDetective.prototype.listServices =
  292. function flockWebDetective_listServices()
  293. {
  294.   var svcEnum = {
  295.     _arr: [],
  296.     QueryInterface: function (aIID) {
  297.       if (aIID.equals(Ci.nsISupports)) return this;
  298.       if (aIID.equals(Ci.nsISimpleEnumerator)) return this;
  299.       throw Cr.NS_ERROR_NO_INTERFACE;
  300.     },
  301.     hasMoreElements: function () {
  302.       return (this._arr.length > 0);
  303.     },
  304.     getNext: function () {
  305.       return {
  306.         QueryInterface: function (aIID) {
  307.           if (aIID.equals(Ci.nsISupports)) return this;
  308.           if (aIID.equals(Ci.nsISupportsPrimitive)) return this;
  309.           if (aIID.equals(Ci.nsISupportsString)) return this;
  310.           throw Cr.NS_ERROR_NO_INTERFACE;
  311.         },
  312.         type: Ci.nsISupportsPrimitive.TYPE_STRING,
  313.         data: this._arr.shift(),
  314.         toString: function () { return this.data; }
  315.       };
  316.     }
  317.   };
  318.   for (var svcName in this.mDetectFiles) {
  319.     svcEnum._arr.push(svcName);
  320.   }
  321.   return svcEnum;
  322. }
  323.  
  324. flockWebDetective.prototype.getVersionForService =
  325. function flockWebDetective_getVersionForService(aServiceName)
  326. {
  327.   return (this.mVersions[aServiceName]) ? this.mVersions[aServiceName] : null;
  328. }
  329. // END flockIWebDetective interface
  330.  
  331.  
  332. // BEGIN helper functions
  333. flockWebDetective.prototype.innerDetect =
  334. function flockWebDetective_innerDetect(aServiceName, aType, aDocument, aForm, aResults)
  335. {
  336.   if (!this.mEnabled) return false;
  337.   var ruleMatch = false;
  338.   var ruleDoc = this.mRules[aServiceName];
  339.   if (ruleDoc) {
  340.     // Cache the rules for faster retrieval next time
  341.     if (!ruleDoc.rules) {
  342.       ruleDoc.rules = [];
  343.     }
  344.     if (!ruleDoc.rules[aType]) {
  345.       ruleDoc.rules[aType] = this.getRulesOfType(ruleDoc, aType);
  346.     }
  347.     var rules = ruleDoc.rules[aType];
  348.     DEBUG("found "+rules.length+ " rule(s) of type '"+aType+"'");
  349.     for (var r = 0; (r < rules.length) && !ruleMatch; r++) {
  350.       var allCondsMatch = true;
  351.       if (rules[r].conditionsEl) {
  352.  
  353.         if (!rules[r].conditions) {
  354.           // This will be used for cacheing the conditions functions
  355.           rules[r].conditions = [];
  356.         }
  357.  
  358.         // First check the URL conditions
  359.         if (!rules[r].conditions["url"]) {
  360.           // Cache the URL conditions for faster retrieval next time
  361.           rules[r].conditions["url"] = this.getURLConditionsForRule(rules[r]);
  362.         }
  363.         var urlConds = rules[r].conditions["url"];
  364.         DEBUG("rule["+r+"]("+aType+") has "+urlConds.length+" URL condition(s)");
  365.         if (urlConds.length > 0) {
  366.           if (aDocument && aDocument instanceof Components.interfaces.nsIDOMHTMLDocument) {
  367.             var ios = Components.classes["@mozilla.org/network/io-service;1"]
  368.                                 .getService(Components.interfaces.nsIIOService);
  369.             var uri = ios.newURI(aDocument.URL, null, null);
  370.             for (var c = 0; (c < urlConds.length) && allCondsMatch; c++) {
  371.               if (!urlConds[c](uri)) {
  372.                 DEBUG("URL condition ["+c+"] failed");
  373.                 allCondsMatch = false;
  374.               }
  375.             }
  376.             if (!allCondsMatch) continue;
  377.           } else {
  378.             // There is no URL, so URL conditions automatically fail
  379.             allCondsMatch = false;
  380.           }
  381.         }
  382.         if (!allCondsMatch) continue;
  383.  
  384.         // Check Form conditions
  385.         if (!rules[r].conditions["form"]) {
  386.           // Cache the Form conditions for faster retrieval next time
  387.           rules[r].conditions["form"] = this.getFormConditionsForRule(rules[r], aForm);
  388.         }
  389.         var formConds = rules[r].conditions["form"];
  390.         DEBUG("rule["+r+"]("+aType+") has "+formConds.length+" Form condition(s)");
  391.         if (formConds.length > 0) {
  392.           if (aForm) {
  393.             for (var c = 0; (c < formConds.length) && allCondsMatch; c++) {
  394.               if (!formConds[c](aForm)) {
  395.                 DEBUG("Form condition ["+c+"] failed");
  396.                 allCondsMatch = false;
  397.               }
  398.             }
  399.           } else {
  400.             // There's no Form, so Form conditions automatically fail
  401.             allCondsMatch = false;
  402.           }
  403.         }
  404.         if (!allCondsMatch) continue;
  405.  
  406.         // Next check the Document conditions
  407.         if (!rules[r].conditions["doc"]) {
  408.           // Cache the Document conditions for faster retrieval next time
  409.           rules[r].conditions["doc"] = this.getDocConditionsForRule(rules[r]);
  410.         }
  411.         var docConds = rules[r].conditions["doc"];
  412.         DEBUG("rule["+r+"]("+aType+") has "+docConds.length+" Document condition(s)");
  413.         if (docConds.length > 0) {
  414.           if (aDocument) {
  415.             for (var c = 0; (c < docConds.length) && allCondsMatch; c++) {
  416.               if (!docConds[c](aDocument)) {
  417.                 DEBUG("Document condition ["+c+"] failed");
  418.                 allCondsMatch = false;
  419.               }
  420.             }
  421.           } else {
  422.             // There's no Document, so Document conditions automatically fail
  423.             allCondsMatch = false;
  424.           }
  425.         }
  426.         if (!allCondsMatch) continue;
  427.  
  428.         // Finally check the Cookie conditions
  429.         if (!rules[r].conditions["cookie"]) {
  430.           // Cache the Cookie conditions for faster retrieval next time
  431.           rules[r].conditions["cookie"] = this.getCookieConditionsForRule(rules[r]);
  432.         }
  433.         var cookieConds = rules[r].conditions["cookie"];
  434.         DEBUG("rule["+r+"]("+aType+") has "+cookieConds.length+" Cookie condition(s)");
  435.         if (cookieConds.length > 0) {
  436.           var relevantCookies = this.getRelevantCookiesForRule(rules[r]);
  437.           for (var c = 0; (c < cookieConds.length) && allCondsMatch; c++) {
  438.             if (!cookieConds[c](relevantCookies)) {
  439.               DEBUG("Cookie condition ["+c+"] failed");
  440.               allCondsMatch = false;
  441.             }
  442.           }
  443.         }
  444.       }
  445.  
  446.       if (allCondsMatch) {
  447.         DEBUG("All conditions matched for rule["+r+"]("+aType+")!");
  448.         ruleMatch = true;
  449.         if (!aResults) break;
  450.         // Now get results
  451.         this.getResultsForRule(aDocument, aForm, rules[r], aResults);
  452.       }
  453.     }
  454.   }
  455.   return ruleMatch;
  456. }
  457.  
  458. flockWebDetective.prototype.getRulesOfType =
  459. function flockWebDetective_getRulesOfType(aRulesDoc, aType)
  460. {
  461.   var rules = [];
  462.   var detectElements = aRulesDoc.getElementsByTagName("detect");
  463.   for (var i = 0; i < detectElements.length; i++) {
  464.     var detect = detectElements.item(i);
  465.     detect = detect.QueryInterface(Components.interfaces.nsIDOMElement);
  466.     if (aType == detect.getAttribute("type")) {
  467.       DEBUG("found <detect type='"+aType+"'>");
  468.       for (var j = 0; j < detect.childNodes.length; j++) {
  469.         var child = detect.childNodes.item(j);
  470.         try {
  471.           child.QueryInterface(Components.interfaces.nsIDOMElement);
  472.           if (child.tagName == "conditions") {
  473.             detect.conditionsEl = child;
  474.           }
  475.           if (child.tagName == "results") {
  476.             detect.resultsEl = child;
  477.           }
  478.         } catch (ex) {
  479.           // Do nothing
  480.         }
  481.       }
  482.       if (!detect.conditionsEl) {
  483.         detect.conditionsEl = detect;
  484.       }
  485.       if (!detect.resultsEl) {
  486.         detect.resultsEl = detect;
  487.       }
  488.       rules[rules.length] = detect;
  489.     }
  490.   }
  491.   return rules;
  492. }
  493.  
  494. flockWebDetective.prototype.getURLConditionsForRule =
  495. function flockWebDetective_getURLConditionsForRule(aRule)
  496. {
  497.   var conditions = [];
  498.  
  499.   // Look for the first "url" element (ignore any others)
  500.   var urlEls = aRule.conditionsEl.getElementsByTagName("url");
  501.   if (urlEls.length) {
  502.     var urlEl = urlEls.item(0);
  503.     conditions = this.addStandardConds(conditions, urlEl, function (aURI) { return aURI.spec; });
  504.     if (urlEl.hasAttribute("domain")) {
  505.       conditions[conditions.length] = function (aURI) {
  506.         var domain = urlEl.getAttribute("domain");
  507.         if (aURI.host == domain) return true;
  508.         var idx = aURI.host.indexOf("."+domain);
  509.         if (idx < 1) return false;
  510.         return ((idx + domain.length + 1) == aURI.host.length);
  511.       };
  512.     }
  513.     var tags = ["host", "path", "querystring"];
  514.     var tagFuncs = [];
  515.     tagFuncs["host"] = function (aURI) { return aURI.host; };
  516.     tagFuncs["path"] = function (aURI) { return aURI.path; };
  517.     tagFuncs["querystring"] = function (aURI) { return aURI.path.substring(aURI.path.indexOf("?")); };
  518.     for (var tag in tagFuncs) {
  519.       var elements = urlEl.getElementsByTagName(tag);
  520.       for (var e = 0; e < elements.length; e++) {
  521.         var elem = elements.item(e);
  522.         conditions = this.addStandardConds(conditions, elem, tagFuncs[tag]);
  523.       }
  524.     }
  525.     var regexpEls = urlEl.getElementsByTagName("regexp");
  526.     for (var i = 0; i < regexpEls.length; i++) {
  527.       var regexpEl = regexpEls.item(i);
  528.       var rExpr = this.getRegexpFromNode(regexpEl);
  529.       var inst = this;
  530.       conditions[conditions.length] = function (aURI) {
  531.         return inst.doRegexpMatch(aURI.spec, rExpr, null, null);
  532.       };
  533.     }
  534.   }
  535.   return conditions;
  536. }
  537.  
  538. flockWebDetective.prototype.getFormConditionsForRule =
  539. function flockWebDetective_getFormConditionsForRule(aRule, aForm)
  540. {
  541.   var conditions = [];
  542.  
  543.   // Look for the first "form" element (ignore any others)
  544.   var formEls = aRule.conditionsEl.getElementsByTagName("form");
  545.   if (formEls.length) {
  546.     var formEl = formEls.item(0);
  547.     // Iterate through all the children of "form"
  548.     for (var i = 0; i < formEl.childNodes.length; i++) {
  549.       var formChild = formEl.childNodes.item(i);
  550.       try {
  551.         formChild.QueryInterface(Components.interfaces.nsIDOMElement);
  552.       } catch (ex) {
  553.         continue;
  554.       }
  555.       DEBUG("Found <conditions><form><"+formChild.tagName+">");
  556.       switch (formChild.tagName) {
  557.         case "xpath":
  558.         {
  559.           conditions[conditions.length] = this.createXPathFormCondition(formChild);
  560.         }; break;
  561.         case "field":
  562.         {
  563.           conditions[conditions.length] = this.createFieldCondition(formChild);
  564.         }; break;
  565.       }
  566.     }
  567.   }
  568.   return conditions;
  569. }
  570.  
  571. flockWebDetective.prototype.createXPathFormCondition =
  572. function flockWebDetective_createXPathFormCondition(aXPathNode)
  573. {
  574.   var inst = this;
  575.   return function (aForm) {
  576.     if (!aXPathNode.xpathExpr) {
  577.       aXPathNode.xpathExpr = inst.getXPathExpression(aXPathNode);
  578.     }
  579.     if (!aXPathNode.xpathFunc) {
  580.       aXPathNode.xpathFunc = inst.createXPathCondition(aXPathNode.xpathExpr);
  581.     }
  582.     if (!aForm.xpathPrefix) {
  583.       aForm.xpathPrefix = inst.getXPathPrefix(aForm);
  584.       DEBUG("Got XPath prefix for this form: "+aForm.xpathPrefix);
  585.     }
  586.     return aXPathNode.xpathFunc(aForm.ownerDocument, aForm.xpathPrefix);
  587.   };
  588. }
  589.  
  590. const FIELD_ATTRIBUTES = [ "name", "type", "class" ];
  591.  
  592. flockWebDetective.prototype.createFieldCondition =
  593. function flockWebDetective_createFieldCondition(aFieldNode)
  594. {
  595.   var inst = this;
  596.   return function (aForm) {
  597.     return (inst.getMatchingFormField(aFieldNode, aForm) != null);
  598.   };
  599. }
  600.  
  601. flockWebDetective.prototype.getMatchingFormField =
  602. function flockWebDetective_getMatchingFormField(aFieldNode, aForm)
  603. {
  604.   var formFields = aForm.elements;
  605.   for (var i = 0; i < formFields.length; i++) {
  606.     var field = formFields.item(i)
  607.       .QueryInterface(Components.interfaces.nsIDOMElement);
  608.     if (aFieldNode.hasAttribute("tagname")) {
  609.       if (field.tagName.toLowerCase() != aFieldNode.getAttribute("tagname").toLowerCase()) {
  610.         // This form field does not match the pattern, so skip it
  611.         DEBUG("tagname does NOT match ["+field.tagName+" != "+aFieldNode.getAttribute("tagname")+"]");
  612.         continue;
  613.       }
  614.     }
  615.     if (aFieldNode.hasAttribute("fieldid")) {
  616.       if ( !field.hasAttribute("id") ||
  617.            (aFieldNode.getAttribute("fieldid") != field.getAttribute("id")) )
  618.       {
  619.         // This form field does not match the pattern, so skip it
  620.         DEBUG("fieldid does NOT match");
  621.         continue;
  622.       }
  623.       // The fields have matching ids!
  624.     }
  625.     var matchesAllAttributes = true;
  626.     for (var j = 0; j < FIELD_ATTRIBUTES.length; j++) {
  627.       var attr = FIELD_ATTRIBUTES[j];
  628.       if (aFieldNode.hasAttribute(attr)) {
  629.         if ( !field.hasAttribute(attr) ||
  630.              (aFieldNode.getAttribute(attr) != field.getAttribute(attr)) )
  631.         {
  632.           // This form field does not match the pattern, so skip it
  633.           DEBUG("'"+attr+"' does NOT match");
  634.           matchesAllAttributes = false;
  635.           break;
  636.         }
  637.       }
  638.     }
  639.     if (!matchesAllAttributes) continue;
  640.     // This form field matches the pattern
  641.     return field;
  642.   }
  643.   // Didn't find any fields matching the pattern
  644.   return null;
  645. }
  646.  
  647. flockWebDetective.prototype.getDocConditionsForRule =
  648. function flockWebDetective_getDocConditionsForRule(aRule)
  649. {
  650.   var conditions = [];
  651.  
  652.   // Document conditions can occur either under a "document" element, or else
  653.   // at the top level if compact syntax is being used.
  654.   var docCondNodes = this.getDocSubNodes(aRule.conditionsEl);
  655.  
  656.   // Iterate through all the "document" sub nodes
  657.   for (var i = 0; i < docCondNodes.length; i++) {
  658.     var docChild = docCondNodes[i];
  659.     try {
  660.       docChild.QueryInterface(Components.interfaces.nsIDOMElement);
  661.     } catch (ex) {
  662.       continue;
  663.     }
  664.     DEBUG("Found <conditions><document><"+docChild.tagName+">");
  665.     switch (docChild.tagName) {
  666.       case "xpath":
  667.       {
  668.         var xpathExpr = this.getXPathExpression(docChild);
  669.         if (xpathExpr) {
  670.           conditions[conditions.length] = this.createXPathCondition(xpathExpr);
  671.         }
  672.       }; break; // END "xpath" case
  673.  
  674.       case "regexp":
  675.       {
  676.         var rExpr = this.getRegexpFromNode(docChild);
  677.         if (this.isValidMatchRegexp(rExpr)) {
  678.           var isMultiLine = (docChild.getAttribute("multiline") == "true");
  679.           if (isMultiLine) {
  680.             conditions[conditions.length] =
  681.               this.createMultilineRegexpCondition(rExpr);
  682.           } else {
  683.             conditions[conditions.length] = this.createRegexpCondition(rExpr);
  684.           }
  685.         } else {
  686.           DEBUG("Condition will fail due to INVALID regexp: "+rExpr);
  687.           conditions[conditions.length] = function (aDocument) {
  688.             return false;
  689.           };
  690.         }
  691.       }; break; // END "regexp" case
  692.     }
  693.   }
  694.   return conditions;
  695. }
  696.  
  697. flockWebDetective.prototype.createXPathCondition =
  698. function flockWebDetective_createXPathCondition(aXPathExpr)
  699. {
  700.   var func = function (aDocument, aXPathPrefix) {
  701.     if (aDocument.noDOM) {
  702.       DEBUG("Document has no DOM, can't use XPath!!");
  703.       return false;
  704.     }
  705.     aDocument.QueryInterface(Components.interfaces.nsIDOMXPathEvaluator);
  706.     var allXPath = (aXPathPrefix) ? aXPathPrefix+aXPathExpr : aXPathExpr;
  707.     DEBUG("Evaluating XPath statement: "+allXPath);
  708.     var result;
  709.     if (aDocument instanceof Components.interfaces.nsIDOMHTMLDocument) {
  710.       result = aDocument.evaluate(allXPath, aDocument.body, null, 0, null);
  711.     } else {
  712.       DEBUG(aDocument.documentElement.namespaceURI);
  713.       var _xs = Components.classes["@mozilla.org/xmlextras/xmlserializer;1"]
  714.                           .createInstance(Components.interfaces.nsIDOMSerializer);;
  715.       var xml = _xs.serializeToString(aDocument.documentElement);
  716.       var namespace = "xmlns=\""+aDocument.documentElement.namespaceURI+"\"";
  717.       var temp = xml.indexOf(namespace);
  718.       aDocument.doc = null;
  719.       //need to strip out the namespace otherwise aDocument.evaluate will fail
  720.       if (temp > 0) {
  721.         var new_xml = xml.substring(0, temp)
  722.                     + xml.substring((temp + namespace.length), xml.length);
  723.         var parser = Components.classes["@mozilla.org/xmlextras/domparser;1"]
  724.                                .createInstance(Components.interfaces.nsIDOMParser);
  725.         aDocument.doc = parser.parseFromString(new_xml, "text/xml");
  726.       }
  727.       result = aDocument.evaluate( allXPath,
  728.                                    (aDocument.doc ? aDocument.doc : aDocument),
  729.                                    null,
  730.                                    Components.interfaces.nsIDOMXPathResult.ANY_TYPE,
  731.                                    null );
  732.     }
  733.     try {
  734.       result = result.QueryInterface(Components.interfaces.nsIDOMXPathResult);
  735.     } catch (ex) {
  736.       return false;
  737.     }
  738.     DEBUG(" - got an nsIDOMXPathResult of type ["+result.resultType+"]");
  739.     switch (result.resultType) {
  740.       case Components.interfaces.nsIDOMXPathResult.STRING_TYPE:
  741.       {
  742.         DEBUG("  result.stringValue = "+result.stringValue);
  743.         return true;
  744.       }; break;
  745.       case Components.interfaces.nsIDOMXPathResult.UNORDERED_NODE_ITERATOR_TYPE:
  746.       {
  747.         var node = result.iterateNext();
  748.         try {
  749.           node.QueryInterface(Components.interfaces.nsIDOMNode);
  750.           DEBUG(" - XPath matching node!" + node.nodeValue);
  751.           // Found a matching node!
  752.           return true;
  753.         } catch (ex) {
  754.           DEBUG(ex);
  755.           DEBUG(" - NO match on XPath " +result);
  756.         }
  757.         return false;
  758.       }; break;
  759.       default:
  760.         DEBUG("WARNING!!! Unhandled XPath result type: "+result.resultType);
  761.     }
  762.     return false;
  763.   };
  764.   return func;
  765. }
  766.  
  767. /**
  768.  * Generates a function for testing a regular expression against each line of
  769.  * a document in succession.
  770.  */
  771. flockWebDetective.prototype.createRegexpCondition =
  772. function flockWebDetective_createRegexpCondition(aRegExp)
  773. {
  774.   var inst = this;
  775.   var func = function createRegexpCond_inner(aDocument) {
  776.     var html = aDocument.documentElement.innerHTML;
  777.     var lines = html.split(/[\r\n]+/);
  778.     for (var i = 0; i < lines.length; i++) {
  779.       if (inst.doRegexpMatch(lines[i], aRegExp, null, null)) {
  780.         return true;
  781.       }
  782.     }
  783.     return false;
  784.   };
  785.   return func;
  786. }
  787.  
  788. /**
  789.  * Generates a function for testing a "multiline" regular expression against
  790.  * the entire text of a document.
  791.  */
  792. flockWebDetective.prototype.createMultilineRegexpCondition =
  793. function flockWebDetective_createMultilineRegexpCondition(aRegExp)
  794. {
  795.   var inst = this;
  796.   var func = function createMultilineRegexpCond_inner(aDocument) {
  797.     var html = aDocument.documentElement.innerHTML;
  798.     var oneString = html.replace(/[\r\n]/g, "");
  799.     return inst.doRegexpMatch(oneString, aRegExp, null, null);
  800.   };
  801.   return func;
  802. }
  803.  
  804. flockWebDetective.prototype.getCookieConditionsForRule =
  805. function flockWebDetective_getCookieConditionsForRule(aRule)
  806. {
  807.   var conditions = [];
  808.   var cookieEls = aRule.conditionsEl.getElementsByTagName("cookie");
  809.   for (var e = 0; e < cookieEls.length; e++) {
  810.     var cookieEl = cookieEls.item(e);
  811.     var cHost = null;
  812.     if (cookieEl.hasAttribute("host")) {
  813.       cHost = cookieEl.getAttribute("host");
  814.     }
  815.     var cName = null;
  816.     if (cookieEl.hasAttribute("name")) {
  817.       cName = cookieEl.getAttribute("name");
  818.     }
  819.     var cNoMatch = (cookieEl.getAttribute("nomatch") == "true");
  820.     this.addCookieCondition(conditions, cHost, cName, cNoMatch);
  821.   }
  822.   return conditions;
  823. }
  824.  
  825. flockWebDetective.prototype.addCookieCondition =
  826. function flockWebDetective_addCookieCondition(aConditions, aHost, aName, aNoMatch)
  827. {
  828.   aConditions[aConditions.length] = function (aRelevantCookies) {
  829.     var foundMatch = false;
  830.     for (var c = 0; (c < aRelevantCookies.length) && !foundMatch; c++) {
  831.       DEBUG("Testing cookie ["+aRelevantCookies[c].host+"]["+aRelevantCookies[c].name+"]");
  832.       DEBUG(" against rule ["+aNoMatch+"]["+aHost+"]["+aName+"]");
  833.       if ((aRelevantCookies[c].host == aHost) &&
  834.           (aRelevantCookies[c].name == aName))
  835.       {
  836.         foundMatch = true;
  837.       }
  838.     }
  839.     if (foundMatch && !aNoMatch) return true;
  840.     if (!foundMatch && aNoMatch) return true;
  841.     return false;
  842.   };
  843. }
  844.  
  845. flockWebDetective.prototype.getRelevantCookiesForRule =
  846. function flockWebDetective_getRelevantCookiesForRule(aRule)
  847. {
  848.   var relevant = [];
  849.   var cookieEls = aRule.conditionsEl.getElementsByTagName("cookie");
  850.   for (var e = 0; e < cookieEls.length; e++) {
  851.     var host = cookieEls.item(e).getAttribute("host");
  852.     var name = cookieEls.item(e).getAttribute("name");
  853.     //DEBUG("Cookies with host="+host+" and name="+name+" are relevant");
  854.     if (!relevant[host]) {
  855.       relevant[host] = [];
  856.     }
  857.     relevant[host][name] = true;
  858.   }
  859.   var relevantCookies = [];
  860.   var cookEnum = this.cookieMgr.enumerator;
  861.   var cookieCount = 0;
  862.   while (cookEnum.hasMoreElements()) {
  863.     var cookie = cookEnum.getNext()
  864.                          .QueryInterface(Components.interfaces.nsICookie);
  865.     cookieCount++;
  866.     //DEBUG(" - filtering cookie ["+cookie.host+"]["+cookie.name+"]");
  867.     if (relevant[cookie.host] && relevant[cookie.host][cookie.name]) {
  868.       relevantCookies[relevantCookies.length] = cookie;
  869.     }
  870.   }
  871.   DEBUG("Found "+relevantCookies.length+" relevant cookies out of "+cookieCount+" total");
  872.   return relevantCookies;
  873. }
  874.  
  875. flockWebDetective.prototype.addStandardConds =
  876. function flockWebDetective_addStandardConds(aConditionsArray, aDOMElement, paramFunc)
  877. {
  878.   aDOMElement.QueryInterface(Components.interfaces.nsIDOMElement);
  879.   if (aDOMElement.hasAttribute("equals")) {
  880.     aConditionsArray[aConditionsArray.length] = function (param) {
  881.       var input = paramFunc(param);
  882.       DEBUG("condition: "+input+" == "+aDOMElement.getAttribute("equals"));
  883.       return (input == aDOMElement.getAttribute("equals"));
  884.     };
  885.   }
  886.   if (aDOMElement.hasAttribute("contains")) {
  887.     aConditionsArray[aConditionsArray.length] = function (param) {
  888.       var input = paramFunc(param);
  889.       DEBUG("condition: "+input+" ~= "+aDOMElement.getAttribute("contains"));
  890.       return (input.indexOf(aDOMElement.getAttribute("contains")) != -1);
  891.     };
  892.   }
  893.   return aConditionsArray;
  894. }
  895.  
  896. flockWebDetective.prototype.getResultsForRule =
  897. function flockWebDetective_getResultsForRule(aDocument, aForm, aRule, aResults)
  898. {
  899.   DEBUG(".getResultsForRule()");
  900.   aResults.QueryInterface(Components.interfaces.nsIWritablePropertyBag2);
  901.   if (!aRule.resultsEl) return;
  902.  
  903.   if (aDocument) {
  904.  
  905.     // Get URL results
  906.     if (!aRule.urlResults) {
  907.       aRule.urlResults = aRule.resultsEl.getElementsByTagName("url");
  908.     }
  909.     var workingData = {};
  910.     for (var u = 0; u < aRule.urlResults.length; u++) {
  911.       var urlEl = aRule.urlResults.item(u);
  912.       DEBUG("found <results><url>");
  913.       for (var i = 0; i < urlEl.childNodes.length; i++) {
  914.         var child = urlEl.childNodes.item(i);
  915.         try {
  916.           child.QueryInterface(Components.interfaces.nsIDOMElement);
  917.         } catch (ex) {
  918.           continue;
  919.         }
  920.         DEBUG("found <results><url><"+child.tagName+">");
  921.         switch (child.tagName) {
  922.           case "regexp":
  923.           {
  924.             this.getRegexpResults(null, aDocument.URL, child, aResults, workingData);
  925.           }; break;
  926.         }
  927.       }
  928.     }
  929.  
  930.     // Get Document results
  931.     workingData = {};
  932.     if (!aRule.docResults) {
  933.       aRule.docResults = this.getDocSubNodes(aRule.resultsEl);
  934.     }
  935.     for (var d = 0; d < aRule.docResults.length; d++) {
  936.       var child = aRule.docResults[d];
  937.       try {
  938.         child.QueryInterface(Components.interfaces.nsIDOMElement);
  939.       } catch (ex) {
  940.         continue;
  941.       }
  942.       DEBUG("found <results><document><"+child.tagName+">");
  943.       switch (child.tagName) {
  944.         case "regexp":
  945.         {
  946.           this.getRegexpResults(aDocument, aDocument.documentElement.innerHTML, child, aResults, workingData);
  947.         }; break;
  948.         case "xpath":
  949.         {
  950.           this.getXPathResults(aDocument, child, aResults, workingData);
  951.         }; break;
  952.       }
  953.     }
  954.   }
  955.  
  956.   if (aForm) {
  957.     // Get Form results
  958.     var workingData = {};
  959.     var formEls = aRule.resultsEl.getElementsByTagName("form");
  960.     for (var f = 0; f < formEls.length; f++) {
  961.       var formEl = formEls.item(f);
  962.       DEBUG("found <results><form>");
  963.       for (var i = 0; i < formEl.childNodes.length; i++) {
  964.         var child = formEl.childNodes.item(i);
  965.         try {
  966.           child.QueryInterface(Components.interfaces.nsIDOMElement);
  967.         } catch (ex) {
  968.           continue;
  969.         }
  970.         DEBUG("found <results><form><"+child.tagName+">");
  971.         switch (child.tagName) {
  972.           case "xpath":
  973.           {
  974.             this.getXPathResults(aForm, child, aResults, workingData);
  975.           }; break;
  976.           case "field":
  977.           {
  978.             this.getFieldResults(aForm, child, aResults);
  979.           }; break;
  980.         }
  981.       }
  982.     }
  983.   }
  984.  
  985.   // TODO: Get Cookie results...
  986. }
  987.  
  988. flockWebDetective.prototype.getDocSubNodes =
  989. function flockWebDetective_getDocSubNodes(aNode)
  990. {
  991.   var resultNodes = [];
  992.   var documentEls = aNode.getElementsByTagName("document");
  993.   for (var i = 0; i < documentEls.length; i++) {
  994.     var docEl = documentEls.item(i);
  995.     for (var j = 0; j < docEl.childNodes.length; j++) {
  996.       var child = docEl.childNodes.item(j);
  997.       try {
  998.         child.QueryInterface(Components.interfaces.nsIDOMElement);
  999.       } catch (ex) {
  1000.         continue;
  1001.       }
  1002.       resultNodes.push(child);
  1003.     }
  1004.   }
  1005.   // Any children of the result node that are NOT in [ conditions, url,
  1006.   // document, form, cookie ] are considered document conditions.
  1007.   for (var i = 0; i < aNode.childNodes.length; i++) {
  1008.     var child = aNode.childNodes.item(i);
  1009.     try {
  1010.       child.QueryInterface(Components.interfaces.nsIDOMElement);
  1011.     } catch (ex) {
  1012.       continue;
  1013.     }
  1014.     switch (child.tagName.toLowerCase()) {
  1015.       case "conditions":
  1016.       case "url":
  1017.       case "document":
  1018.       case "form":
  1019.       case "cookie":
  1020.         continue;
  1021.       default:
  1022.         resultNodes.push(child);
  1023.     }
  1024.   }
  1025.   return resultNodes;
  1026. }
  1027.  
  1028. flockWebDetective.prototype.getRegexpResults =
  1029. function flockWebDetective_getRegexpResults(aDecorEl, aString, aRegexpNode, aResults, aData)
  1030. {
  1031.   // Cache the regexp
  1032.   if (!aRegexpNode.rExpr) {
  1033.     aRegexpNode.rExpr = this.getRegexpFromNode(aRegexpNode);
  1034.   }
  1035.   // Cache whether it is valid
  1036.   if (!aRegexpNode.isValid) {
  1037.     aRegexpNode.isValid = this.isValidMatchRegexp(aRegexpNode.rExpr);
  1038.   }
  1039.   if (!aRegexpNode.isValid) {
  1040.     DEBUG("Can't get results due to INVALID regexp: "+aRegexpNode.rExpr);
  1041.     return;
  1042.   }
  1043.   // Cache the list of variables we are looking for
  1044.   if (!aRegexpNode.reVars) {
  1045.     aRegexpNode.reVars = [];
  1046.     aRegexpNode.postProcess = [];
  1047.     for (var re = 1; ; re++) {
  1048.       if (aRegexpNode.hasAttribute("re"+re)) {
  1049.         // Look for variable declarations as attributes
  1050.         aRegexpNode.reVars[re] = aRegexpNode.getAttribute("re"+re);
  1051.         continue;
  1052.       } else {
  1053.         // Look for variable declarations as elements
  1054.         var reTags = aRegexpNode.getElementsByTagName("re"+re);
  1055.         if (reTags.length) {
  1056.           var reTag = reTags.item(0);
  1057.           if (reTag.hasAttribute("name")) {
  1058.             aRegexpNode.reVars[re] = reTag.getAttribute("name");
  1059.             if (reTag.hasAttribute("processing")) {
  1060.               aRegexpNode.postProcess.push(reTag);
  1061.             }
  1062.             continue;
  1063.           }
  1064.         }
  1065.       }
  1066.       // There are no more variables to be gotten from this regexp
  1067.       break;
  1068.     }
  1069.   }
  1070.   // See if we have already decorated with the values for any of these vars
  1071.   var allDecorated = false;
  1072.   if (aDecorEl && aDecorEl._flock_decorations) {
  1073.     allDecorated = true;
  1074.     for (var i = 0; i < aRegexpNode.reVars.length; i++) {
  1075.       var val = aDecorEl._flock_decorations[aRegexpNode.reVars[i]];
  1076.       if (val) {
  1077.         DEBUG("Found a decoration for ["+aRegexpNode.reVars[i]+"] = "+val);
  1078.         aResults.setPropertyAsAString(aRegexpNode.reVars[i], val);
  1079.       } else {
  1080.         allDecorated = false;
  1081.       }
  1082.     }
  1083.   }
  1084.   if (allDecorated) {
  1085.     DEBUG("Found all the vars as decorations! No need to run regexp...");
  1086.     return;
  1087.   }
  1088.   // Ensure that a value exists -- at least an empty string -- for each var
  1089.   for (var i = 0; i < aRegexpNode.reVars.length; i++) {
  1090.     try {
  1091.       aResults.getPropertyAsAString(aRegexpNode.reVars[i]);
  1092.     } catch (ex) {
  1093.       aResults.setPropertyAsAString(aRegexpNode.reVars[i], "");
  1094.     }
  1095.   }
  1096.   DEBUG("Getting results using regexp: "+aRegexpNode.rExpr);
  1097.   if (aRegexpNode.rExpr) {
  1098.     var isMultiLine = (aRegexpNode.getAttribute("multiline") == "true");
  1099.     var isMultiValent = (aRegexpNode.getAttribute("multivalent") == "true");
  1100.     if (isMultiLine) {
  1101.       if (!aData.oneString) {
  1102.         aData.oneString = aString.replace(/[\r\n]/g, "");
  1103.       }
  1104.       this.doRegexpMatch( aData.oneString,
  1105.                           aRegexpNode.rExpr,
  1106.                           aRegexpNode.reVars,
  1107.                           aResults,
  1108.                           aDecorEl );
  1109.     } else {
  1110.       // isMultiLine == false
  1111.       if (!aData.lines) {
  1112.         aData.lines = aString.split(/[\r\n]/);
  1113.       }
  1114.       DEBUG("There are "+aData.lines.length+" lines to test with regexp");
  1115.       for (var line = 0; line < aData.lines.length; line++) {
  1116.         //DEBUG("line "+line+" has "+aData.lines[line].length+" characters");
  1117.         if (this.doRegexpMatch( aData.lines[line],
  1118.                                 aRegexpNode.rExpr,
  1119.                                 aRegexpNode.reVars,
  1120.                                 aResults,
  1121.                                 aDecorEl,
  1122.                                 isMultiValent ))
  1123.         {
  1124.           for (var i = 0; i < aRegexpNode.postProcess.length; i++) {
  1125.             this.postProcess(aRegexpNode.postProcess[i], aResults, aDecorEl);
  1126.           }
  1127.           break;
  1128.         }
  1129.       }
  1130.     }
  1131.   }
  1132. }
  1133.  
  1134. flockWebDetective.prototype.setResult =
  1135. function flockWebDetective_setResult(aResults, aName, aValue, aDecorEl)
  1136. {
  1137.   aResults.setPropertyAsAString(aName, aValue);
  1138.   if (aDecorEl) {
  1139.     if (!aDecorEl._flock_decorations) {
  1140.       aDecorEl._flock_decorations = [];
  1141.     }
  1142.     aDecorEl._flock_decorations[aName] = aValue;
  1143.   }
  1144. }
  1145.  
  1146. flockWebDetective.prototype.postProcess =
  1147. function flockWebDetective_postProcess(aRENode, aResults, aDecorEl)
  1148. {
  1149.   var name = aRENode.getAttribute("name");
  1150.   DEBUG(".postProcess('"+name+"')");
  1151.   var processes = aRENode.getAttribute("processing").split(",");
  1152.   for (var i = 0; i < processes.length; i++) {
  1153.     var oldVal = aResults.getPropertyAsAString(name);
  1154.     var newVal = oldVal;
  1155.     DEBUG(" - process: "+processes[i]);
  1156.     switch (processes[i].toLowerCase()) {
  1157.       case "unescape":
  1158.         newVal = unescape(oldVal);
  1159.         break;
  1160.       case "toupper":
  1161.       case "touppercase":
  1162.         newVal = oldVal.toUpperCase();
  1163.         break;
  1164.       case "tolower":
  1165.       case "tolowercase":
  1166.         newVal = oldVal.toLowerCase();
  1167.         break;
  1168.       case "subst":
  1169.         // There must be a CDATA block with the substitution regexp
  1170.         var substRegexp = null;
  1171.         for (var j = 0; (j < aRENode.childNodes.length) && !substRegexp; j++) {
  1172.           var child = aRENode.childNodes.item(j);
  1173.           if (child.nodeType ==
  1174.               Components.interfaces.nsIDOMNode.CDATA_SECTION_NODE)
  1175.           {
  1176.             DEBUG("  - substitution regexp: "+child.nodeValue);
  1177.             substRegexp = this.parseSubstRegexp(child.nodeValue);
  1178.           }
  1179.         }
  1180.         if (substRegexp) {
  1181.           DEBUG("   pattern: "+substRegexp.pattern);
  1182.           DEBUG("   replacement: "+substRegexp.replace);
  1183.           // Convert to JS-compatible syntax
  1184.           var replacement = substRegexp.replace;
  1185.           for (var r = 1; r < 10; r++) {
  1186.             var replacementRE = "/\/"+r+"/g";
  1187.             replacement = replacement.replace(eval(replacementRE), "$"+r);
  1188.           }
  1189.           // Do the substitution
  1190.           newVal = oldVal.replace(eval(substRegexp.pattern), replacement);
  1191.         }
  1192.         break;
  1193.       default:
  1194.         DEBUG("WARNING: unhandled processing directive: "+processes[i]);
  1195.     }
  1196.     DEBUG("   - new value: "+newVal);
  1197.     this.setResult(aResults, name, newVal, aDecorEl);
  1198.   }
  1199. }
  1200.  
  1201. flockWebDetective.prototype.parseSubstRegexp =
  1202. function flockWebDetective_parseSubstRegexp(aRegexpString)
  1203. {
  1204.   if (!aRegexpString.match(/^s\/((\\\/|[^\/])+)\/((\\\/|[^\/])*)\/[gim]{0,3}$/)) {
  1205.     DEBUG("INVALID substitution regexp: "+aRegexpString);
  1206.     return null;
  1207.   }
  1208.   return {
  1209.     pattern: "/"+RegExp.$1+"/",
  1210.     replace: RegExp.$3
  1211.   };
  1212. }
  1213.  
  1214. const TEST_FOR_ATTRIBS = [
  1215.   "class",
  1216.   "name",
  1217.   "value",
  1218.   "action",
  1219.   "method"
  1220. ];
  1221.  
  1222. flockWebDetective.prototype.getXPathPrefix =
  1223. function flockWebDetective_getXPathPrefix(aHTMLElement)
  1224. {
  1225.   if (!aHTMLElement) return "";
  1226.   DEBUG(".getXPathPrefix('"+aHTMLElement+"')");
  1227.   var el = aHTMLElement.QueryInterface(Components.interfaces.nsIDOMElement);
  1228.   var doc = el.ownerDocument;
  1229.   var xpathExpr = "//"+el.tagName.toLowerCase();
  1230.   DEBUG(".getXPathPrefix" + xpathExpr);
  1231.   if (el.hasAttribute("id")) {
  1232.     xpathExpr += "[@id=\""+el.getAttribute("id")+"\"]";
  1233.   } else {
  1234.     for (var i = 0; i < TEST_FOR_ATTRIBS.length; i++) {
  1235.       var attrib = TEST_FOR_ATTRIBS[i];
  1236.       if (el.hasAttribute(attrib)) {
  1237.         xpathExpr += "[@"+attrib+"=\""+el.getAttribute(attrib)+"\"]";
  1238.       }
  1239.     }
  1240.     // This may not be enough to uniquely identify the node, so do the parent
  1241.     // as well...
  1242.     if (el.parentNode && (el.parentNode.tagName.toLowerCase() != "body")) {
  1243.       xpathExpr = this.getXPathPrefix(el.parentNode) + xpathExpr.substring(1);
  1244.     }
  1245.   }
  1246.   return xpathExpr;
  1247. }
  1248.  
  1249. flockWebDetective.prototype.getXPathExpression =
  1250. function flockWebDetective_getXPathExpression(aXPathNode)
  1251. {
  1252.   var xpathExpr = null;
  1253.   if (aXPathNode.hasAttribute("match")) {
  1254.     xpathExpr = aXPathNode.getAttribute("match");
  1255.   } else {
  1256.     for (var j = 0; (j < aXPathNode.childNodes.length) && !xpathExpr; j++) {
  1257.       if ( aXPathNode.childNodes.item(j).nodeType ==
  1258.            Components.interfaces.nsIDOMNode.CDATA_SECTION_NODE )
  1259.       {
  1260.         xpathExpr = aXPathNode.childNodes.item(j).nodeValue;
  1261.       }
  1262.     }
  1263.   }
  1264.   return xpathExpr;
  1265. }
  1266.  
  1267. flockWebDetective.prototype.getXPathResults =
  1268. function flockWebDetective_getXPathResults(aDocumentOrElement, aXPathNode, aResults, aData)
  1269. {
  1270.   // First check to see if we have already cached this result as a decoration
  1271.   // on aDocumentOrElement
  1272.   var name = null;
  1273.   if (aXPathNode.hasAttribute("name")) {
  1274.     name = aXPathNode.getAttribute("name");
  1275.     if ( aDocumentOrElement._flock_decorations &&
  1276.          aDocumentOrElement._flock_decorations[name] )
  1277.     {
  1278.       DEBUG("Using decorated value for '"+name+"'");
  1279.       aResults.setPropertyAsAString(name, aDocumentOrElement._flock_decorations[name]);
  1280.       return;
  1281.     }
  1282.   }
  1283.   // No cached value, so press on
  1284.   var doc;
  1285.   var needPrefix = false;
  1286.   try {
  1287.     aDocumentOrElement.QueryInterface(Components.interfaces.nsIDOMDocument);
  1288.     doc = aDocumentOrElement;
  1289.   } catch (ex) {
  1290.     doc = aDocumentOrElement.ownerDocument;
  1291.     needPrefix = true;
  1292.   }
  1293.   if (!aDocumentOrElement.xpathPrefix && needPrefix) {
  1294.     aDocumentOrElement.xpathPrefix = this.getXPathPrefix(aDocumentOrElement);
  1295.   }
  1296.   if (!aDocumentOrElement.xpathPrefix) {
  1297.     aDocumentOrElement.xpathPrefix = "";
  1298.   }
  1299.   if (!aXPathNode.xpathExpr) {
  1300.     aXPathNode.xpathExpr = this.getXPathExpression(aXPathNode);
  1301.   }
  1302.   DEBUG("aDocumentOrElement.xpathPrefix " + aDocumentOrElement.xpathPrefix);
  1303.   var xpathExpr = aXPathNode.xpathExpr;
  1304.   if (aDocumentOrElement.xpathPrefix) {
  1305.     xpathExpr = aDocumentOrElement.xpathPrefix + aXPathNode.xpathExpr;
  1306.   }
  1307.   if (xpathExpr) {
  1308.     var extract = null;
  1309.     if (aXPathNode.hasAttribute("extract")) {
  1310.       extract = aXPathNode.getAttribute("extract");
  1311.     }
  1312.     var multivalent = false;
  1313.     if (aXPathNode.hasAttribute("multivalent")) {
  1314.       multivalent = aXPathNode.getAttribute("multivalent");
  1315.     }
  1316.     var snippets = this.getXMLSnippetsFromXPath(doc, xpathExpr, extract);
  1317.     DEBUG(" XPath got us "+snippets.length+" snippets to check " + multivalent);
  1318.     if (snippets.length && name) {
  1319.       if (!multivalent) {
  1320.         this.setResult(aResults, name, snippets[0], aDocumentOrElement);
  1321.       } else {
  1322.         this.setResult(aResults, name, snippets, aDocumentOrElement);
  1323.       }
  1324.     }
  1325.     // Now let's see if there's a regexp subnode
  1326.     for (var i = 0; i < aXPathNode.childNodes.length; i++) {
  1327.       var child = aXPathNode.childNodes.item(i);
  1328.       try {
  1329.         child.QueryInterface(Components.interfaces.nsIDOMElement);
  1330.       } catch (ex) {
  1331.         continue;
  1332.       }
  1333.       if (child.tagName == "regexp") {
  1334.         DEBUG(" This XPath statement has a Regexp too!");
  1335.         for (var j = 0; j < snippets.length; j++) {
  1336.           this.getRegexpResults(aDocumentOrElement, snippets[j], child, aResults, aData);
  1337.         }
  1338.       }
  1339.     }
  1340.   }
  1341. }
  1342.  
  1343. const VALID_EXTRACT_VALUES = {
  1344.   "value": true,
  1345.   "nodeValue": true,
  1346. };
  1347.  
  1348. flockWebDetective.prototype.getXMLSnippetsFromXPath =
  1349. function flockWebDetective_getXMLSnippetsFromXPath(aDocument, aXPathExpr, aExtract)
  1350. {
  1351.   DEBUG("Looking for snippets that match this XPath: "+aXPathExpr+" "+aDocument);
  1352.   var snippets = [];
  1353.   if (aDocument.noDOM) {
  1354.     DEBUG("Document has no DOM, can't use XPath!!");
  1355.     return;
  1356.   }
  1357.   aDocument.QueryInterface(Components.interfaces.nsIDOMXPathEvaluator);
  1358.   var result;
  1359.   if (aDocument instanceof Components.interfaces.nsIDOMHTMLDocument) {
  1360.     result = aDocument.evaluate(aXPathExpr, aDocument.body, null, 0, null);
  1361.   } else {
  1362.     result = aDocument.evaluate( aXPathExpr,
  1363.                                  (aDocument.doc ? aDocument.doc : aDocument),
  1364.                                  null,
  1365.                                  Components.interfaces.nsIDOMXPathResult.ANY_TYPE,
  1366.                                  null );
  1367.   }
  1368.   try {
  1369.     result = result.QueryInterface(Components.interfaces.nsIDOMXPathResult);
  1370.   } catch (ex) {
  1371.     return;
  1372.   }
  1373.   switch (result.resultType) {
  1374.     case Components.interfaces.nsIDOMXPathResult.STRING_TYPE:
  1375.     {
  1376.       DEBUG("  result.stringValue = "+result.stringValue);
  1377.       snippets.push(result.stringValue);
  1378.     }; break;
  1379.     case Components.interfaces.nsIDOMXPathResult.UNORDERED_NODE_ITERATOR_TYPE:
  1380.     {
  1381.       var node;
  1382.       while (node = result.iterateNext()) {
  1383.         try {
  1384.           node.QueryInterface(Components.interfaces.nsIDOMNode);
  1385.         } catch (ex) {
  1386.           continue;
  1387.         }
  1388.         var value;
  1389.         if ( node.hasChildNodes() &&
  1390.              (node.childNodes.length > 1
  1391.               || (node.childNodes.length == 1
  1392.                 && !node.firstChild instanceof Components.interfaces.nsIDOMText)) )
  1393.         {
  1394.           var _xs = Components.classes["@mozilla.org/xmlextras/xmlserializer;1"]
  1395.                               .createInstance(Components.interfaces.nsIDOMSerializer);
  1396.           value = _xs.serializeToString(node);
  1397.         } else if ( node.childNodes.length == 1 &&
  1398.                     node.firstChild instanceof Components.interfaces.nsIDOMText )
  1399.         {
  1400.           value = node.firstChild.textContent;
  1401.         } else {
  1402.           value = node.nodeValue;
  1403.         }
  1404.         if (aExtract) {
  1405.           if (VALID_EXTRACT_VALUES[aExtract]) {
  1406.             value = node[aExtract];
  1407.           } else if ( aExtract.indexOf("attribute:") == 0 &&
  1408.                       node instanceof Components.interfaces.nsIDOMElement )
  1409.           {
  1410.             var attrName = aExtract.substring(10);
  1411.             value = node.getAttribute(attrName);
  1412.           }
  1413.         }
  1414.         DEBUG(" - XPath matching node: "+ value);
  1415.         snippets.push(value);
  1416.       }
  1417.     }; break;
  1418.     default:
  1419.       DEBUG("WARNING!!! Unhandled XPath result type: "+result.resultType);
  1420.   }
  1421.   return snippets;
  1422. }
  1423.  
  1424. flockWebDetective.prototype.getFieldResults =
  1425. function flockWebDetective_getFieldResults(aForm, aFieldNode, aResults)
  1426. {
  1427.   var varname = null;
  1428.   if (aFieldNode.hasAttribute("extractas")) {
  1429.     varname = aFieldNode.getAttribute("extractas");
  1430.   } else if (aFieldNode.hasAttribute("name")) {
  1431.     varname = aFieldNode.getAttribute("name");
  1432.   } else if (aFieldNode.hasAttribute("fieldid")) {
  1433.     varname = aFieldNode.getAttribute("fieldid");
  1434.   }
  1435.   if (varname) {
  1436.     var field = this.getMatchingFormField(aFieldNode, aForm);
  1437.     if (field instanceof Components.interfaces.nsIDOMHTMLInputElement) {
  1438.       field.QueryInterface(Components.interfaces.nsIDOMHTMLInputElement);
  1439.       DEBUG(".getFieldResults() - extracting form field value ["+varname+"] = "+field.value);
  1440.       aResults.setPropertyAsAString(varname, field.value);
  1441.     }
  1442.   }
  1443. }
  1444.  
  1445. flockWebDetective.prototype.getRegexpFromNode =
  1446. function flockWebDetective_getRegexpFromNode(aRegexpNode)
  1447. {
  1448.   var rExpr = null;
  1449.   if (aRegexpNode.hasAttribute("expression")) {
  1450.     rExpr = aRegexpNode.getAttribute("expression");
  1451.   } else {
  1452.     for (var j = 0; j < aRegexpNode.childNodes.length; j++) {
  1453.       if ( aRegexpNode.childNodes.item(j).nodeType ==
  1454.            Components.interfaces.nsIDOMNode.CDATA_SECTION_NODE )
  1455.       {
  1456.         rExpr = aRegexpNode.childNodes.item(j).nodeValue;
  1457.       }
  1458.     }
  1459.   }
  1460.   return rExpr;
  1461. }
  1462.  
  1463. /**
  1464.  * This does not do 'true' regexp validation, but rather just enough to ensure
  1465.  * that executing this string will not allow arbitrary code execution.
  1466.  */
  1467. flockWebDetective.prototype.isValidMatchRegexp =
  1468. function flockWebDetective_isValidMatchRegexp(aRegexpString)
  1469. {
  1470.   DEBUG("Testing for validity: "+aRegexpString);
  1471.   return aRegexpString.match(/^\/(\\\/|[^\/])+\/[gim]{0,3}$/);
  1472. }
  1473.  
  1474. flockWebDetective.prototype.doRegexpMatch =
  1475. function flockWebDetective_doRegexpMatch( aString, aRegexp, aResultFields,
  1476.                                           aResults, aDecorEl, aIsMultiValent )
  1477. {
  1478.   //DEBUG(aString);
  1479.   if (aString.match(eval(aRegexp))) {
  1480.     DEBUG(".doRegexpMatch() MATCH!");
  1481.     if (aResultFields && aResults) {
  1482.       for (var i = 1; i < aResultFields.length; i++) {
  1483.         var fieldName = aResultFields[i];
  1484.         if (aIsMultiValent) {
  1485.           for (var j = 1; ; j++) {
  1486.             try {
  1487.               aResults.getPropertyAsAString(fieldName+j);
  1488.             } catch (ex) {
  1489.               fieldName += j;
  1490.               break;
  1491.             }
  1492.           }
  1493.         }
  1494.         DEBUG(".doRegexpMatch() found [ "+fieldName+" ] = "+eval("RegExp.$"+i));
  1495.         this.setResult(aResults, fieldName, eval("RegExp.$"+i), aDecorEl);
  1496.       }
  1497.     }
  1498.     return true;
  1499.   }
  1500.   //DEBUG(".doRegexpMatch() NO-match.");
  1501.   return false;
  1502. }
  1503.  
  1504. flockWebDetective.prototype.loadStrings =
  1505. function flockWebDetective_loadStrings(aServiceName)
  1506. {
  1507.   this.mStrings[aServiceName] = [];
  1508.   var stringsEls = this.mRules[aServiceName].getElementsByTagName("strings");
  1509.   for (var i = 0; i < stringsEls.length; i++) {
  1510.     var stringsEl = stringsEls.item(i);
  1511.     stringsEl.QueryInterface(Components.interfaces.nsIDOMElement);
  1512.     var stringEls = stringsEl.getElementsByTagName("string");
  1513.     for (var j = 0; j < stringEls.length; j++) {
  1514.       var stringEl = stringEls.item(j);
  1515.       stringEl.QueryInterface(Components.interfaces.nsIDOMElement);
  1516.       if (stringEl.hasAttribute("name")) {
  1517.         var stringName = stringEl.getAttribute("name");
  1518.         var foundValue = null;
  1519.         if (stringEl.hasAttribute("value")) {
  1520.           foundValue = stringEl.getAttribute("value");
  1521.         }
  1522.         var longestTextValue = "";
  1523.         for (var k = 0; (k < stringEl.childNodes.length) && !foundValue; k++) {
  1524.           var child = stringEl.childNodes.item(k);
  1525.           switch (child.nodeType) {
  1526.             case Components.interfaces.nsIDOMNode.TEXT_NODE:
  1527.               if (child.nodeValue.length > longestTextValue.length) {
  1528.                 longestTextValue = child.nodeValue;
  1529.               }
  1530.               break;
  1531.             case Components.interfaces.nsIDOMNode.CDATA_SECTION_NODE:
  1532.               foundValue = stringEl.childNodes.item(k).nodeValue;
  1533.               break;
  1534.           }
  1535.         }
  1536.         if (!foundValue) {
  1537.           foundValue = longestTextValue;
  1538.         }
  1539.         this.mStrings[aServiceName][stringName] = foundValue;
  1540.         DEBUG( ".loadStrings('"+aServiceName+"'): found string '"+stringName
  1541.                +"': "+this.mStrings[aServiceName][stringName] );
  1542.       }
  1543.     }
  1544.   }
  1545. }
  1546.  
  1547. flockWebDetective.prototype.loadSessionCookies =
  1548. function flockWebDetective_loadSessionCookies(aServiceName)
  1549. {
  1550.   this.mSessionCookies[aServiceName] = [];
  1551.   var cookiesEls = this.mRules[aServiceName].getElementsByTagName("sessioncookies");
  1552.   for (var i = 0; i < cookiesEls.length; i++) {
  1553.     var cookiesEl = cookiesEls.item(i);
  1554.     cookiesEl.QueryInterface(Components.interfaces.nsIDOMElement);
  1555.     var cookieEls = cookiesEl.getElementsByTagName("cookie");
  1556.     for (var j = 0; j < cookieEls.length; j++) {
  1557.       var cookieEl = cookieEls.item(j);
  1558.       cookieEl.QueryInterface(Components.interfaces.nsIDOMElement);
  1559.       var c = {
  1560.         host: cookieEl.getAttribute("host"),
  1561.         name: cookieEl.getAttribute("name"),
  1562.         path: cookieEl.getAttribute("path")
  1563.       };
  1564.       DEBUG( ".loadSessionCookies('"+aServiceName+"'): host["+c.host+"] name["
  1565.              +c.name+"] path["+c.path+"]" );
  1566.       this.mSessionCookies[aServiceName].push(c);
  1567.     }
  1568.   }
  1569. }
  1570. // END helper functions
  1571.  
  1572. // BEGIN update service functions
  1573.  
  1574. // This is patterned after nsSearchService.js:engineMetadataService
  1575. function makeURI(url) {
  1576.   var ios = Cc['@mozilla.org/network/io-service;1']
  1577.     .getService(Ci.nsIIOService);
  1578.   try {
  1579.     return ios.newURI(url, null, null);
  1580.   } catch (ex) { }
  1581.  
  1582.   return null;
  1583. }
  1584.  
  1585. function createStatement(dbconn, sql) {
  1586.   var stmt = dbconn.createStatement(sql);
  1587.   var wrapper = Cc["@mozilla.org/storage/statement-wrapper;1"]
  1588.     .createInstance(Ci.mozIStorageStatementWrapper);
  1589.  
  1590.   wrapper.initialize(stmt);
  1591.   return wrapper;
  1592. }
  1593.  
  1594. function UpdateMetadataStore() {
  1595.   this.init();
  1596. }
  1597.  
  1598. UpdateMetadataStore.prototype = {
  1599.   init: function UMS_init() {
  1600.     var dbfile = Cc['@mozilla.org/file/directory_service;1']
  1601.       .getService(Ci.nsIProperties).get('ProfD', Ci.nsIFile);
  1602.     dbfile.append('webdetective.sqlite');
  1603.  
  1604.     var storageService = Cc['@mozilla.org/storage/service;1']
  1605.       .getService(Ci.mozIStorageService);
  1606.     this.mDBConn = storageService.openDatabase(dbfile);
  1607.  
  1608.     var schema = 'id INTEGER PRIMARY KEY, servicename STRING, ' +
  1609.                  'name STRING, value STRING';
  1610.  
  1611.     try {
  1612.       this.mDBConn.createTable('webdetect_data', schema);
  1613.     }
  1614.     catch (e) { }
  1615.  
  1616.     this.mGetData = createStatement(this.mDBConn,
  1617.       'SELECT value FROM webdetect_data WHERE servicename = :servicename ' +
  1618.       'AND name = :name');
  1619.     this.mDeleteData = createStatement(this.mDBConn,
  1620.       'DELETE FROM webdetect_data WHERE servicename = :servicename ' +
  1621.       'AND name = :name');
  1622.     this.mInsertData = createStatement(this.mDBConn,
  1623.       'INSERT INTO webdetect_data (servicename, name, value) ' +
  1624.       'VALUES (:servicename, :name, :value)');
  1625.   },
  1626.   getAttr: function UMS_getAttr(serviceName, name) {
  1627.     name = name.toLowerCase();
  1628.  
  1629.     var stmt = this.mGetData;
  1630.     stmt.reset();
  1631.     var pp = stmt.params;
  1632.     pp.servicename = serviceName;
  1633.     pp.name = name;
  1634.  
  1635.     var value = null;
  1636.     if (stmt.step())
  1637.       value = stmt.row.value;
  1638.     stmt.reset();
  1639.     return value;
  1640.   },
  1641.   setAttr: function UMS_setAttr(serviceName, name, value) {
  1642.     name = name.toLowerCase();
  1643.  
  1644.     this.mDBConn.beginTransaction();
  1645.  
  1646.     this.deleteServiceData(serviceName, name);
  1647.  
  1648.     pp = this.mInsertData.params;
  1649.     pp.servicename = serviceName;
  1650.     pp.name = name;
  1651.     pp.value = value;
  1652.     this.mInsertData.step();
  1653.     this.mInsertData.reset();
  1654.  
  1655.     this.mDBConn.commitTransaction();
  1656.   },
  1657.   deleteServiceData: function UMS_deleteServiceData(serviceName, name) {
  1658.     name = name.toLowerCase();
  1659.  
  1660.     var pp = this.mDeleteData.params;
  1661.     pp.servicename = serviceName;
  1662.     pp.name = name;
  1663.     this.mDeleteData.step();
  1664.     this.mDeleteData.reset();
  1665.   },
  1666. }
  1667.  
  1668. flockWebDetective.prototype.startUpdateService =
  1669. function flockWebDetective_startUpdateService()
  1670. {
  1671.   this.updateMetadataStore = new UpdateMetadataStore();
  1672.  
  1673.   var tm = Cc['@mozilla.org/updates/timer-manager;1']
  1674.     .getService(Ci.nsIUpdateTimerManager);
  1675.  
  1676.   var prefs = Cc['@mozilla.org/preferences-service;1']
  1677.     .getService(Ci.nsIPrefBranch)
  1678.   var interval = prefs.getIntPref('flock.service.webdetective.updateinterval');
  1679.  
  1680.   var seconds = interval * 3600;
  1681.   tm.registerTimer('web-detective-update-timer', this, seconds);
  1682. }
  1683.  
  1684. /**
  1685.  * Stevo : This may be a bit clunky right now, we have 2 variables (updatesOk and fileCount) that
  1686.  *  are used to maintain if all updates are successful, and when to report back the results to aListener
  1687.  *  if aListener is valid. So for each service we send off a request and have a listener that onSuccess
  1688.  *  decrements the fileCount and when fileCount is <= 0 calls the onSuccess or onError depending on the
  1689.  *  state of updatesOk, onError will decrement fileCount and set updatesOk to false and when fileCount is <= 0
  1690.  *  it will report back onError to the aListener.
  1691.  */
  1692. flockWebDetective.prototype.checkForUpdates =
  1693. function flockWebDetective_checkForUpdates(/*boolean */forceUpdates, /*flockIListener */aListener)
  1694. {
  1695.   var now = Date.now();
  1696.   var updatesOk = true;  // If any updates fail then this will be false.
  1697.   var fileCount = 0;
  1698.  
  1699.   var serviceUpdateListener = {
  1700.     onSuccess : function(aSubject, aTopic) {
  1701.       fileCount = fileCount - 1;
  1702.       if (fileCount <= 0) {
  1703.         if (aListener) {
  1704.           if (updatesOk) {
  1705.             aListener.onSuccess(null, null);
  1706.           } else {
  1707.             aListener.onError(null, null, null);
  1708.           }
  1709.         }
  1710.       }
  1711.     },
  1712.     onError : function(aSubject, aTopic, aError) {
  1713.       fileCount = fileCount - 1;
  1714.       updatesOk = false;
  1715.       if (fileCount <= 0) {
  1716.         if (aListener) {
  1717.           aListener.onError(null, null, null);
  1718.         }
  1719.       }
  1720.     }
  1721.   };
  1722.  
  1723.   for (var serviceName in this.mDetectFiles) {
  1724.     var expire = this.updateMetadataStore.getAttr(serviceName, 'updateexpire');
  1725.     if ((expire && expire > now) && !forceUpdates)
  1726.       continue;
  1727.  
  1728.     fileCount = fileCount + 1;
  1729.     if (aListener) {
  1730.       aListener.onStart(null, serviceName);
  1731.       this.sendUpdateRequest(serviceName, serviceUpdateListener);
  1732.     } else {
  1733.       this.sendUpdateRequest(serviceName);
  1734.     }
  1735.   }
  1736. }
  1737.  
  1738. flockWebDetective.prototype.getUpdateInfo =
  1739. function flockWebDetective_getUpdateInfo(aServiceName)
  1740. {
  1741.   var updateEls = this.mRules[aServiceName].getElementsByTagName('update');
  1742.   for (var i = 0; i < updateEls.length; i++) {
  1743.     var updateEl = updateEls.item(i);
  1744.     updateEl.QueryInterface(Ci.nsIDOMElement);
  1745.     var url = updateEl.getAttribute('url');
  1746.     var testURI = makeURI(url);
  1747.     if (testURI) {
  1748.       var interval = updateEl.getAttribute('interval');
  1749.       var info = {
  1750.         url: testURI.spec,
  1751.         interval: interval ? interval : DEFAULT_UPDATE_INTERVAL
  1752.       };
  1753.       return info;
  1754.     }
  1755.   }
  1756.  
  1757.   var url = DEFAULT_UPDATE_SERVER + this.mDetectFiles[aServiceName].leafName;
  1758.   var info = {
  1759.     url: makeURI(url).spec,
  1760.     interval: DEFAULT_UPDATE_INTERVAL
  1761.   };
  1762.   return info;
  1763. }
  1764.  
  1765. flockWebDetective.prototype.sendUpdateRequest =
  1766. function flockWebDetective_sendUpdateRequest(aServiceName, aListener)
  1767. {
  1768.   var updateInfo = this.getUpdateInfo(aServiceName);
  1769.  
  1770.   var hr = Cc['@mozilla.org/xmlextras/xmlhttprequest;1']
  1771.     .createInstance(Ci.nsIXMLHttpRequest);
  1772.   hr.backgroundRequest = true;
  1773.   hr.open('GET', updateInfo.url);
  1774.  
  1775.   var lastModified = this.updateMetadataStore.getAttr(aServiceName,
  1776.                                                       'updatelastmodified');
  1777.   hr.setRequestHeader('If-Modified-Since', lastModified);
  1778.  
  1779.   var self = this;
  1780.   var updateInterval = updateInfo.interval;
  1781.  
  1782.   hr.onload = function(event) {
  1783.     var req = event.target;
  1784.     if (req.responseXML) {
  1785.       self.updateDetectFile(aServiceName, req.responseText, updateInterval);
  1786.       if (aListener) {
  1787.         aListener.onSuccess(null,aServiceName);
  1788.       }
  1789.     } else {
  1790.       if (aListener) {
  1791.         aListener.onError(null,aServiceName,null);
  1792.       }
  1793.     }
  1794.   }
  1795.  
  1796.   hr.send(null);
  1797. }
  1798.  
  1799. flockWebDetective.prototype.updateDetectFile =
  1800. function flockWebDetective_updateDetectFile(serviceName, contents, updateInterval)
  1801. {
  1802.   if (!contents) {
  1803.     this.updateMetadataStore.setAttr(serviceName, 'updateexpire',
  1804.                                      Date.now() + updateInterval * 86400000);
  1805.     return;
  1806.   }
  1807.  
  1808.   try {
  1809.     var detectFile = this.mDetectFiles[serviceName];
  1810.  
  1811.     var ostream = Cc['@mozilla.org/network/safe-file-output-stream;1']
  1812.       .createInstance(Ci.nsIFileOutputStream);
  1813.     ostream.init(detectFile, PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE, 0644, 0);
  1814.  
  1815.     var converter = Cc['@mozilla.org/intl/scriptableunicodeconverter']
  1816.       .createInstance(Ci.nsIScriptableUnicodeConverter);
  1817.     converter.charset = 'UTF-8';
  1818.  
  1819.     var convdata = converter.ConvertFromUnicode(contents) + converter.Finish();
  1820.  
  1821.     ostream.write(convdata, convdata.length);
  1822.  
  1823.     if (ostream instanceof Ci.nsISafeOutputStream) {
  1824.       ostream.finish();
  1825.     } else {
  1826.       ostream.close();
  1827.     }
  1828.  
  1829.     this.loadDetectFile(detectFile);
  1830.  
  1831.     var updateInfo = this.getUpdateInfo(serviceName);
  1832.     this.updateMetadataStore.setAttr(serviceName, 'updateexpire',
  1833.                                      Date.now() + updateInfo.interval * 86400000);
  1834.  
  1835.     this.updateMetadataStore.setAttr(serviceName, 'updatelastmodified',
  1836.                                      (new Date()).toUTCString());
  1837.   }
  1838.   catch (e) { }
  1839. }
  1840. // END update service functions
  1841.  
  1842. // ========== END flockWebDetective class ==========
  1843.  
  1844.  
  1845.  
  1846. // =========================================
  1847. // ========== BEGIN XPCOM Support ==========
  1848. // =========================================
  1849.  
  1850. var Module = {
  1851.   _firstTime: true,
  1852.   registerSelf: function(aCompMgr, aFileSpec, aLocation, aType)
  1853.   {
  1854.     if (this._firstTime) {
  1855.       this._firstTime = false;
  1856.       throw Components.results.NS_ERROR_FACTORY_REGISTER_AGAIN;
  1857.     }
  1858.     aCompMgr = aCompMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar);
  1859.     aCompMgr.registerFactoryLocation(CLASS_ID, CLASS_NAME, CONTRACT_ID, aFileSpec, aLocation, aType);
  1860.  
  1861.     var categoryManager = Components.classes["@mozilla.org/categorymanager;1"]
  1862.                                     .getService(Components.interfaces.nsICategoryManager);
  1863.     categoryManager.addCategoryEntry("flock-startup", CLASS_NAME, "service," + CONTRACT_ID, true, true);
  1864.   },
  1865.  
  1866.   unregisterSelf: function(aCompMgr, aLocation, aType)
  1867.   {
  1868.     aCompMgr = aCompMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar);
  1869.     aCompMgr.unregisterFactoryLocation(CLASS_ID, aLocation);
  1870.   },
  1871.  
  1872.   getClassObject: function(aCompMgr, aCID, aIID)
  1873.   {
  1874.     if (!aIID.equals(Components.interfaces.nsIFactory)) {
  1875.       throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  1876.     }
  1877.     if (aCID.equals(CLASS_ID)) {
  1878.       return Factory;
  1879.     }
  1880.     throw Components.results.NS_ERROR_NO_INTERFACE;
  1881.   },
  1882.  
  1883.   canUnload: function(aCompMgr) { return true; }
  1884. };
  1885.  
  1886. var Factory = {
  1887.   createInstance: function(aOuter, aIID)
  1888.   {
  1889.     if (aOuter != null) {
  1890.       throw Components.results.NS_ERROR_NO_AGGREGATION;
  1891.     }
  1892.     return (new flockWebDetective()).QueryInterface(aIID);
  1893.   }
  1894. };
  1895.  
  1896. function NSGetModule(aCompMgr, aFileSpec)
  1897. {
  1898.   return Module;
  1899. }
  1900.  
  1901. // ========== END XPCOM Support ==========
  1902.